package LearnAlgorithm.i_递归进阶andDFSand剪枝and回溯;

import java.util.Scanner;

/*
你一定听说过“数独”游戏。
如下图所示，玩家需要根据9×9盘面上的已知数字，推理出所有剩余空格的数字，
并满足每一行、每一列、每一个同色九宫内的数字均含1-9，不重复。
数独的答案都是唯一的，所以，多个解也称为无解。
本图的数字据说是芬兰数学家花了3个月的时间设计出来的较难的题目。
但对会使用计算机编程的你来说，恐怕易如反掌了。
本题的要求就是输入数独题目，程序输出数独的唯一解。
我们保证所有已知数据的格式都是合法的，并且题目有唯一的解。

格式要求，输入9行，每行9个数字，0代表未知，其它数字为已知。

输出9行，每行9个数字表示数独的解。
输入：

005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700

程序应该输出：

145327698
839654127
672918543
496185372
218473956
753296481
367542819
984761235
521839764

再例如，输入：

800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400

程序应该输出：

812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452
 */
public class hDFS_数独游戏by回溯and剪枝 {
	public static void main(String[] args) {
		hDFS_数独游戏by回溯and剪枝 test = new hDFS_数独游戏by回溯and剪枝();
		test.useDFS_Sudoku();
	}
	
	public void useDFS_Sudoku() {
		Scanner scanner = new Scanner(System.in);
		char[][] table = new char[9][9];
		for (int i = 0; i < table.length; i++) {
			table[i] = scanner.nextLine().toCharArray();
		}
		DFS_Sudoku(table, 0, 0);
	}
	
	public void DFS_Sudoku(char[][] table, int x, int y) {
		if (x == 9) {//递归出口，x最大的有效值 = 8，所以当x=9时，根据自动换行原理，数独已经全部填写完成
			print(table);//打印填完的数独
			System.exit(0);//中断程序
		}
		if (table[x][y] == '0') {//判断[x,y]处是否为空
			for (int i = 1; i < 10; i++) {//对这个位置，1~9逐个尝试
				if (check(table, x, y, i)) {//check()方法判断，尝试的结果，是否符合数独规则
					table[x][y] = (char) ('0' + i);//符合，修改当前位置的数字
					DFS_Sudoku(table, x + (y + 1) / 9, (y + 1) % 9);//继续尝试下一个位置[x,y+1](自动换行)
					/*
					当y=8时，尝试递归地调用DFS_Sudoku()，结果是：
					x + (y + 1) / 9 = x + (8 + 1) / 9 = x + 1
					(y + 1) % 9		= (8 + 1) % 9 = 0
					y指向了第0列
					x指向了下一行
					很巧妙地自动换行
					 */
//					table[x][y] = '0';
				}
			}
			table[x][y] = '0';//对[x,y]进行回溯，回溯成“0”
			/*
			这里地回溯语句，放在for循环外面，是可以的
			经过我的思考，放在for循环外面：
				不会引发“check()方法因为之前填过的数字，导致产生错误的boolean”
				因为对这个，递归树的每一个节点而言，在for循环中：
					“填‘某数字A’的某一个孩子失败了”；此时其他孩子接着尝试，其他孩子填的“某数字B”会直接覆盖A
					所以才不用把回溯放在for的if中
				很抽象地解释就是：“每个节点都只有一个子节点的‘残留’，‘残留’会被‘尝试’覆盖，所以不必担心check()发病”
			 */
		} else {//判断[x,y]处不是空；别忘了！
			DFS_Sudoku(table, x + (y + 1) / 9, (y + 1) % 9);
		}
	}
	
	public boolean check(char[][] table, int x, int y, int i) {
		for (int current = 0; current < 9; current++) {
			if (table[x][current] == (char) ('0' + i)) {//检查当前列col
				return false;
			}
			if (table[current][y] == (char) ('0' + i)) {//检查当前行row
				return false;
			}
			
		}
		for (int row = (x / 3) * 3; row < (x / 3) * 3 + 3; row++) {//检查九宫格
			for (int col = (y / 3) * 3; col < (y / 3) * 3 + 3; col++) {
				if (table[row][col] == (char) ('0' + i)) {
					return false;
				}
			}
		}
		return true;
	}
	
	public void print(char[][] table) {
		for (int i = 0; i < 9; i++) {
			System.out.println(new String(table[i]));
		}
	}
}
